﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


using Engine.Graphics;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.ComponentModel;
using Engine.Physics;
using Engine;

namespace Estadio.GameObjects
{
    /// <summary>
    /// A basic ball that can be controlled specifying four buttons directions.
    /// </summary>
    public class Ball : MeshEntity, ICollisionable, IPhysicEntity
    {
        protected Func<float> left;
        protected Func<float> right;
        protected Func<float> up;
        protected Func<float> down;

        #region Physics

        public float Drag { get; set; }
        public float Mass { get; set; }

        #region IPhysicEntity Members

        public Vector3 Acceleration
        {
            get;
            set;
        }


        public Vector3 Velocity
        {
            get;
            set;
        }

        public event EventHandler<CollisionEventArgs> Collision;

        public void OnCollision(CollisionEventArgs args)
        {
            if (Collision != null)
                Collision(this, args);
        }

        public event EventHandler<CollisionEventArgs> Colliding;

        public void OnColliding(CollisionEventArgs args)
        {
            if (Colliding != null)
                Colliding(this, args);
        }
        #endregion

        private Vector3 velocity = Vector3.Zero;
        #endregion

        #region Constants

        const float SphereVelocity = 2;
        const float SphereRadius = 0.08f;

        #endregion

        public Ball(Entity parent, Model model)
            : base(parent, model.Meshes[0], Matrix.Identity)
        {
            this.Scale = Vector3.One * SphereRadius;
            Drag = 0.97f;
            Mass = 1.0f;

            left = () => 0;
            right = () => 0;
            up = () => 0;
            down = () => 0;
        }

        public override void Update(GameTime gameTime)
        {
            //return;
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

            Vector3 force = Vector3.Zero;
            force.X += left();
            force.X -= right();
            force.Z -= up();
            force.Z += down();
            force *= 50.0f;

            Vector3 acceleration = force / Mass;
            velocity += acceleration * dt;

            // Apply psuedo drag
            velocity *= Drag;

            //// Apply velocity
            //Position += Velocity * elapsed;

            //Vector3 velocity = Vector3.Zero;
            //velocity.X += left();
            //velocity.X -= right();
            //velocity.Z -= up();
            //velocity.Z += down();
            //velocity *= SphereVelocity * dt;

            Vector3 movementDir = Vector3.Normalize(velocity);
            if (float.IsNaN(movementDir.X)) return;
            //this.Direction = movementDir;

            Vector3 currPosition = Position;
            Vector3 newPosition = currPosition + velocity * dt;


            // now we need to roll the ball "forward." to do this, we first calculate
            // how far it has moved.
            float distanceMoved = Vector3.Distance(currPosition, newPosition);

            float theta = distanceMoved / SphereRadius;

            Vector3 movementDirRight = -Vector3.Cross(movementDir, Vector3.Up);
            // finally, we'll roll it by rotating around the sphere's "right" vector.
            Rotation *= Matrix.CreateFromAxisAngle(movementDirRight, theta);


            //Apply the calculated position at the end.
            Position = newPosition;

            base.Update(gameTime);
        }

        public override void Draw(GameTime gameTime)
        {
            //mesh.Draw(gameTime);
            base.Draw(gameTime);
        }

        public void AddControlKeys(
            Func<float> left, 
            Func<float> right,
            Func<float> up,
            Func<float> down)
        {
            this.left = left;
            this.right = right;
            this.up = up;
            this.down = down;
        }


        #region ICollisionable Members

        public CollisionType Type
        {
            get
            {
                return CollisionType.Sphere;
            }
            set
            {
                throw new Exception("Cannot change collision type");
            }
        }

        public BoundingBox BBox
        {
            get { throw new NotImplementedException(); }
        }

        public BoundingSphere Sphere
        {
            get { return this.mesh.BoundingSphere; }
        }

        public Plane Plane
        {
            get { throw new NotImplementedException(); }
        }

        #endregion


    }
}
